Working with Projections and Transformations in Diana

This document aims to show how to transform geographic coordinates to and from screen coordinates for a map projection in Diana.

Installation

To get access to the Python modules that expose Diana's plotting features, you need to install the python-diana package from the local package repository. Either use the package manager to do this, or enter the following in a terminal:

apt-get install python-diana

Some examples can also be found in the python-diana-examples package.

Importing Modules

You can use Diana's functionality from Python by importing the bdiana and plotcommands modules from the metno package. If you are using an old version of ipython (earlier than version 1.0) then it is also useful to import the embed function from the metno.ipython_extensions module. The pyproj module is used to describe projections in a way that Diana understands. We also import the datetime module so that we can pass a time to Diana without needing to obtain one from a data file.


In [1]:
# Import the modules needed to use Diana.
from metno import bdiana, plotcommands
# Import an extension that lets us embed an image into a worksheet.
from metno.ipython_extensions import embed
import datetime, pyproj

Initialisation and Setup

The bdiana module contains the BDiana class. This provides a convenient interface to Diana's functions and also wraps several initialisation steps into one object. To start using Diana, we simply create an instance of this class and load a setup file that describes where various resources are located and set it up using a setup file located on the user's system.


In [2]:
b = bdiana.BDiana()
b.setup("/etc/diana/setup/diana.setup-COMMON")

Plotting a Map

We use a predefined area to display a map, creating plot commands and passing them to the BDiana object. We set the current time as the plot time and plot an image.


In [3]:
a = plotcommands.Area(name="S-Norge")
m = plotcommands.Map(map="Gshhs-Auto", backcolour="white", land="on")
m.setOption("land.colour", "flesh")

b.setPlotCommands([a.text(), m.text()])
b.setPlotTime(datetime.datetime.now())

im = b.plotImage(500, 500)
embed(im)

We obtain the area shown by the plot.


In [4]:
area = b.getPlotArea()

This area is comprised of a projection and a rectangle. We obtain these so that we can work with them.


In [5]:
p = area.P()
r = b.getPlotSize()

We create a new Proj object to describe the projection since this is convenient to use from Python.


In [6]:
pr = pyproj.Proj(p.getProjDefinition())

Painting on the Plot

If we want to annotate the plot using features that are not present in Diana's own annotation features, we can do so by using Qt's own painting API. We import the modules to do this.


In [7]:
from PyQt4.QtCore import Qt, QRect
from PyQt4.QtGui import QFont, QFontMetricsF, QPainter

We want to display the location of Bergen on the image, so we first obtain the projection coordinates from the geographic coordinates using the Proj object we created.


In [8]:
lat = 60.0 + 23.0/60 + 34.0/3600
lon = 5.0 + 19.0/60 + 26.0/3600
x, y = pr(lon, lat)

Since we know the extent of the plotting area (the rectangle) and the size of the image, we can easily transform the projection coordinates to screen (or image) coordinates.


In [9]:
sx = (x - r.x1)/(r.x2 - r.x1) * im.width()
sy = (y - r.y2)/(r.y1 - r.y2) * im.height()

Now we can paint something on the image. We take a copy first, then use QPainter to draw an ellipse at the desired location.


In [10]:
im2 = im.copy()
radius = 8

p = QPainter()
p.begin(im2)
p.setRenderHint(QPainter.Antialiasing)
p.setBrush(Qt.white)
p.drawEllipse(sx - radius, sy - radius, radius * 2, radius * 2)
p.end()

embed(im2)

We can also display text. We take a copy of the new image, determine the width of the text, and draw the text centred in a rectangular area just to the left of the ellipse.


In [11]:
im3 = im2.copy()

f = QFont()
fm = QFontMetricsF(f)
width = fm.width("Bergen")

p = QPainter()
p.begin(im3)
p.drawText(QRect(sx - width - 20, sy - fm.height()/2, width, fm.height()),
           Qt.AlignCenter, "Bergen")
p.end()

embed(im3)

Getting Geographic Coordinates

We can also map image coordinates back to geographic coordinates by first converting the image coordinates to projection coordinates, then use the Proj object to perform an inverse transformation.


In [12]:
cx, cy = im.width()/2.0, im.height()/2.0 # note use of floats
x = r.x1 + (r.x2 - r.x1) * (cx / im.width())
y = r.y2 + (r.y1 - r.y2) * (cy / im.height())
x, y

In [13]:
lon, lat = pr(x, y, inverse = True)
lat, lon